package org.truenewx.tnxjeex.fss.service.storage.aws;

import java.util.Date;
import java.util.List;

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicSessionCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.*;
import com.amazonaws.services.securitytoken.model.Credentials;
import com.amazonaws.services.securitytoken.model.GetSessionTokenRequest;
import com.amazonaws.services.securitytoken.model.GetSessionTokenResult;
import org.truenewx.tnxjee.core.Strings;
import org.truenewx.tnxjee.core.util.DateUtil;
import org.truenewx.tnxjee.core.util.LogUtil;
import org.truenewx.tnxjee.core.util.NetUtil;
import org.truenewx.tnxjee.model.spec.user.UserIdentity;
import org.truenewx.tnxjeex.fss.service.storage.FssStorageAuthorizer;
import org.truenewx.tnxjeex.fss.service.storage.FssStorageProvider;
import org.truenewx.tnxjeex.fss.service.util.FssUtil;

public class AwsFssStorageAuthorizer implements FssStorageAuthorizer {

    private AwsAccount account;
    private String contextUrl;
    private int tempReadExpiredSeconds = 60; // 临时读取时限默认60秒
    private AwsPolicyBuilder policyBuilder;
    private AwsStsRoleAssumer stsRoleAssumer;

    public AwsFssStorageAuthorizer(AwsAccount account, boolean assumeStsRole) {
        this.account = account;
        // 默认的上下文地址以//开头，不包含具体访问协议，与访问者当前使用协议相同
        this.contextUrl = Strings.DOUBLE_SLASH + this.account.getBucketName() + Strings.DOT + this.account.getEndpoint();
        if (assumeStsRole) {
            this.policyBuilder = new AwsPolicyBuilder(account);
            this.stsRoleAssumer = new AwsStsRoleAssumer(account);
        }
    }

    public void setContextUrl(String contextUrl) {
        this.contextUrl = contextUrl;
    }

    public void setTempReadExpiredSeconds(int tempReadExpiredSeconds) {
        this.tempReadExpiredSeconds = tempReadExpiredSeconds;
    }

    @Override
    public FssStorageProvider getProvider() {
        return FssStorageProvider.AWS;
    }

    @Override
    public void authorizePublicRead(String storagePath) {
        storagePath = FssUtil.standardizePath(storagePath);
        try {
            this.account.getObjectClient()
                    .setObjectAcl(this.account.getBucketName(), storagePath, CannedAccessControlList.PublicRead);
        } catch (Exception e) {
            LogUtil.error(getClass(), e);
        }
    }

    @Override
    public String getReadContextUrl() {
        return this.contextUrl;
    }

    @Override
    public String getReadUrl(UserIdentity<?> userIdentity, String storagePath) {
        storagePath = FssUtil.standardizePath(storagePath);
        // 拆分请求参数，确保路径不带参数
        int index = storagePath.indexOf(Strings.QUESTION);
        String parameterString = Strings.EMPTY;
        if (index >= 0) {
            parameterString = storagePath.substring(index + 1);
            storagePath = storagePath.substring(0, index);
        }
        try {
            if (isPublicRead(storagePath)) {
                StringBuilder url = new StringBuilder(getReadContextUrl()).append(Strings.SLASH).append(storagePath);
                if (parameterString.length() > 0) {
                    url.append(Strings.QUESTION).append(parameterString);
                }
                return url.toString();
            } else { // 非公开可读的，授予临时读取权限
                Credentials credentials = getTempCredentials(storagePath);
                BasicSessionCredentials stsCredentials = new BasicSessionCredentials(
                        credentials.getAccessKeyId(), credentials.getSecretAccessKey(), credentials.getSessionToken());
                AWSCredentialsProvider stsCredentialsProvider = new AWSStaticCredentialsProvider(stsCredentials);
                AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
                        this.account.getEndpoint(), this.account.getRegion());
                AmazonS3 client = AmazonS3ClientBuilder.standard()
                        .withEndpointConfiguration(endpointConfiguration)
                        .withCredentials(stsCredentialsProvider).build();
                GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(
                        this.account.getBucketName(), storagePath);
                Date expiration = DateUtil.addSeconds(new Date(), this.tempReadExpiredSeconds);
                request.setExpiration(expiration);
                if (parameterString.length() > 0) {
                    String[] params = parameterString.split(Strings.AND);
                    for (String param : params) {
                        String[] array = param.split(Strings.EQUAL);
                        if (array.length > 1) {
                            request.addRequestParameter(array[0], array[1]);
                        }
                    }
                }
                String url = client.generatePresignedUrl(request).toString();
                url = replaceContextUrl(url, getReadContextUrl());
                return url;
            }
        } catch (Exception e) {
            LogUtil.error(getClass(), e);
        }
        return null;
    }

    private Credentials getTempCredentials(String storagePath) {
        if (this.policyBuilder != null && this.stsRoleAssumer != null) {
            String policyDocument = this.policyBuilder.buildReadDocument(storagePath);
            return this.stsRoleAssumer.assumeRole(policyDocument);
        } else {
            GetSessionTokenRequest request = new GetSessionTokenRequest();
            request.setDurationSeconds(900); // 中国移动云的接口实际上最低只允许900秒，低于900秒的值均会报错
            GetSessionTokenResult result = this.account.getStsClient().getSessionToken(request);
            return result.getCredentials();
        }
    }

    private boolean isPublicRead(String path) {
        path = FssUtil.standardizePath(path);
        try {
            AccessControlList acl = this.account.getObjectClient().getObjectAcl(this.account.getBucketName(), path);
            List<Grant> grants = acl.getGrantsAsList();
            for (Grant grant : grants) {
                if (grant.getGrantee().equals(GroupGrantee.AllUsers) && grant.getPermission().equals(Permission.Read)) {
                    return true;
                }
            }
        } catch (Exception e) {
            LogUtil.error(getClass(), e);
        }
        return false;
    }

    private String replaceContextUrl(String url, String contextUrl) {
        int index = url.indexOf(NetUtil.PROTOCOL_MARK);
        url = url.substring(url.indexOf(Strings.SLASH, index + 3));
        return contextUrl + url;
    }

}
